package tetris;

/******************* Esteban Bett ********************
**************** email: betteramone@hotmail.com ******/

/* Le he agregado diferentes colores a las fichas :), en lugar de usar
 * un vector de enteros utilizo un vector de Colores.
 */

import java.applet.Applet;
import java.lang.*;
import java.util.*;
import java.awt.*;
import java.applet.*;
import java.awt.event.*;

public class Tetris extends Applet
       implements MouseListener, KeyListener, FocusListener
{
  static final int TIEMPO = 1000;

  static final int QUIETO = 0;
  static final int ABAJO = 1;
  static final int DCHA = 2;
  static final int IZDA = 3;
  static final int ROTAR = 4;
  static final int ABAJOR = 5;

  static final int I_INICIAL = 1;
  static final int J_INICIAL = 4;

  static final Color fondo = Color.black;
  static final Font font = new Font("Serif",Font.BOLD,14);
  Color colorFicha; //color de la ficha en movimiento
  Image db;        // doble buffer, dibujamos primero en �l y despues en pantalla (evitamos parpadeos).
  Graphics gdb;

  //Dise�o de cada ficha
  private int[][] fichas = {
   // Figura ****
   {-1,  0, +1, +2}, { 0,  0,  0,  0},
   { 0,  0,  0,  0}, {-2, -1,  0, +1},
   {-1,  0, +1, +2}, { 0,  0,  0,  0},
   { 0,  0,  0,  0}, {-2, -1,  0, +1},

   // Figura ***
   //         *
   { 0,  0,  0, +1}, {-1,  0, +1,  0},
   {-1,  0, +1,  0}, { 0,  0,  0, +1},
   { 0,  0,  0, -1}, {-1,  0, +1,  0},
   { 0,  0, -1, +1}, {-1,  0,  0,  0},
   // Figura  **
   //        **
   {+1, +1,  0,  0}, {-1,  0,  0, +1},
   {-1,  0,  0, +1}, { 0,  0, +1, +1},
   {+1, +1,  0,  0}, {-1,  0,  0, +1},
   {-1,  0,  0, +1}, { 0,  0, +1, +1},
   // Figura  **
   //          **
   { 0,  0, +1, +1}, {-1,  0,  0, +1},
   {-1,  0,  0, +1}, {+1,  0, +1,  0},
   { 0,  0, +1, +1}, {-1,  0,  0, +1},
   {-1,  0,  0, +1}, {+1,  0, +1,  0},
   // Figura  **
   //         **
   { 0,  0, +1, +1}, { 0, +1, +1,  0},
   { 0,  0, +1, +1}, { 0, +1, +1,  0},
   { 0,  0, +1, +1}, { 0, +1, +1,  0},
   { 0,  0, +1, +1}, { 0, +1, +1,  0},
   // Figura  *
   //         ***
   {-1,  0,  0,  0}, {-1, -1,  0, +1},
   {+1, +1,  0, -1}, {-1,  0,  0,  0},
   { 0,  0,  0, +1}, {-1,  0, +1, +1},
   {-1, -1,  0, +1}, {+1,  0,  0,  0},
   // Figura    *
   //         ***
   { 0,  0,  0, -1}, {-1,  0, +1, +1},
   {-1, -1,  0, +1}, {-1,  0,  0,  0},
   {+1,  0,  0,  0}, {-1, -1,  0, +1},
   {-1,  0, +1, +1}, { 0,  0,  0, +1},
  };

  private boolean finJuego = false;
  private Color[][] tablero = new Color[17][10];  //Tablero del juego (matriz de colores).
  private int UNIDAD = 10;
  private int numFilas = 0;

  private int ficha = 0;
  private int orientacion = 0;
  private int posI = 0;
  private int posJ = 0;
  private Reloj reloj;
  public int movimiento = QUIETO;

  /*Devuelve el color asignado a cada ficha en particular*/
  private Color getColor(int numFicha){
    switch(numFicha){
        case 0: return Color.red;
        case 1: return Color.gray;
        case 2: return Color.green;
        case 3: return Color.cyan;
        case 4: return Color.blue;
        case 5: return Color.yellow;
        case 6: return Color.magenta;
        default: return Color.red;
    }
  }

  private int deltaI(int tipoFicha, int orientacion, int indice) {
    return fichas[tipoFicha*8+orientacion*2][indice];
  }

  private int deltaJ(int tipoFicha, int orientacion, int indice) {
    return fichas[tipoFicha*8+orientacion*2+1][indice];
  }

  //Crea una ficha nueva
  private synchronized void nuevaFicha() {
    ficha = (int) ((double) 7* Math.random());  //random entre 0 y 7
    colorFicha = getColor(ficha);               //Obtengo el color para esa ficha.
    orientacion = 0;
    posI = I_INICIAL;
    posJ = J_INICIAL;
    setMovimiento(QUIETO);
    if (puedeIr(posI, posJ)) ponerEn(posI, posJ);
    else {
      //"Se acabo";
      gdb.setColor(Color.green);
      gdb.drawString("Fin del Juego!!!",60,360/2);
      repaint();
      reloj.stop();
      finJuego = true;
    }
  }

  public synchronized void rotar() {
    sacarDe(posI, posJ);
    int aux = orientacion;
    if (orientacion < 3) orientacion++;
    else orientacion = 0;
    if (puedeIr(posI, posJ) == false) orientacion = aux;
    ponerEn(posI, posJ);
    setMovimiento(QUIETO);
  }

  public synchronized void izquierda() {
    //"Izquierda";
    sacarDe(posI, posJ);
    if (puedeIr(posI, posJ-1)) posJ--;
    ponerEn(posI, posJ);
    setMovimiento(QUIETO);
  }

  public synchronized void derecha() {
    //"Derecha";
    sacarDe(posI, posJ);
    if (puedeIr(posI, posJ+1)) posJ++;
    ponerEn(posI, posJ);
    setMovimiento(QUIETO);
  }

  public synchronized void abajo() {
    sacarDe(posI, posJ);
    if (puedeIr(posI+1, posJ)) {
      posI++;
      ponerEn(posI, posJ);
    } else {
      ponerEn(posI, posJ);
      revisaLinea();
      nuevaFicha();
    }
    setMovimiento(QUIETO);
  }

  public synchronized void abajoDeprisa() {
    sacarDe(posI, posJ);
    while (puedeIr(posI+1, posJ)) {
      posI++;
    }
    ponerEn(posI, posJ);
    revisaLinea();
    nuevaFicha();
    setMovimiento(QUIETO);
  }
  public synchronized void abajoLento() {
     sacarDe(posI, posJ);
    if (puedeIr(posI+1, posJ)) {
      posI++;
      ponerEn(posI, posJ);
    } else {
      ponerEn(posI, posJ);
      revisaLinea();
      nuevaFicha();
    }
    setMovimiento(QUIETO);
  }

  //Chequea si hay una linea completa
  private void revisaLinea() {
      for(int i=16; i>0; ) {
          int nOcup = 0;
          for(int j=0; j<10; j++) {
              if (tablero[i][j] != fondo) nOcup++;
          }
          if (nOcup == 10) {
              numFilas++;
              for(int k=i; k >0; k--) {
                  for(int j=0; j<10; j++) {
                      tablero[k][j] = tablero[k-1][j];
                  }
              }
              pintar();
          }else i--;
      }
  }

  private void tableroInicial() {

    finJuego = false;

    for(int i=0; i<17; i++) {
      for(int j=0; j<10; j++) {
        tablero[i][j] = fondo;
      }
    }
    numFilas = 0;
    setMovimiento(QUIETO);
    nuevaFicha();
    pintar();
  }

  private boolean puedeIr(int i, int j) {
    int ni;
    int nj;

    try {
      for (int k=0; k < 4; k++) {
        ni = i + deltaI(ficha, orientacion, k);
        nj = j + deltaJ(ficha, orientacion, k);
        if (tablero[ni][nj] != fondo) return false;
      }
      return true;
    } catch (Exception e) { return false; }
  }

  private void sacarDe(int i, int j) {
    int ni;
    int nj;
    for (int k=0; k < 4; k++) {
      ni = i + deltaI(ficha, orientacion, k);
      nj = j + deltaJ(ficha, orientacion, k);
      tablero[ni][nj] = fondo;
      pintaNada(nj*UNIDAD, ni*UNIDAD);
    }
    repaint();
  }

  private void ponerEn(int i, int j) {
    int ni;
    int nj;

    for (int k=0; k < 4; k++) {
      ni = i + deltaI(ficha, orientacion, k);
      nj = j + deltaJ(ficha, orientacion, k);
      tablero[ni][nj] = colorFicha;
      pintaOcupado(nj*UNIDAD, ni*UNIDAD, colorFicha);
    }
    repaint();
  }

  /* Main */
  public void init()
  {   //creamos el doble buffer
      db = createImage(size().width, size().height);
      gdb = db.getGraphics();

      tableroInicial();

      //Ponemos los listener de eventos al applet.
      addMouseListener(this);
      addKeyListener(this);
      addFocusListener(this);
  }

  //Stop: LLamado por el browser o el applet viewer para informar a este applet que debe detener su ejecuci�n.
  //Este es llamado cuando la pagina web que contiene este applet ha sido reemplazada por otra pagina,
  //y tambien antes que el applet sea destruido.
  public void stop() {
    if(reloj != null){
        reloj.stop();
        reloj = null;
    }
  }

  //Crea e inicia la animacion
  private void arrancaReloj() {
    reloj = new Reloj(this, TIEMPO);
    reloj.start();
  }

  private void paraReloj() {
    reloj.stop();
  }

  //Dibujamos en el buffer y luego mostramos en pantalla.
  public void pintar(){
    UNIDAD = size().height / 18;

    for(int i=0; i<17; i++) {
        for(int j=0; j<10; j++) {
          try {
            if (tablero[i][j] != fondo)  pintaOcupado(j*UNIDAD, i*UNIDAD, tablero[i][j]);
            else  pintaNada(j*UNIDAD, i*UNIDAD);
          }catch (Exception e) {}
        }
    }
    gdb.setColor(Color.lightGray);
    gdb.fillRect(140,18*UNIDAD-20,30,20);
    gdb.setColor(Color.blue);
    gdb.setFont(font);
    gdb.drawString("Filas completadas = "+numFilas, UNIDAD, 18*UNIDAD - 5);

    //Mostramos en pantalla el buffer
    repaint();
  }

  private void pintaOcupado(int x, int y, Color color) {
    int w = UNIDAD;

    gdb.setColor(color);
    gdb.fillRect(x+2, y+2, w-4, w-4);

    gdb.setColor(color.darker());
    gdb.fillRect(x, y, w, 2);
    gdb.fillRect(x+(w-2), y, 2, w);
    gdb.fillRect(x, y+(w-2), w, 2);
    gdb.fillRect(x, y, 2, w);
  }

  //Update: es invocado cada vez que hacemos "reapint()" o cuando la ventana necesite repintarse, por ejemplo
  //        cuando minimizamos y volvemos a maximizar.
  public void update(Graphics g){
      paint(g);
  }

  //Dibuja el buffer en pantalla.
  public void paint(Graphics g){
    g.drawImage(db, 0, 0, size().width, size().height, this);
  }

  private void pintaNada(int x, int y) {
    gdb.setColor(fondo);
    gdb.fillRect(x, y, UNIDAD, UNIDAD);
  }

  public synchronized void setMovimiento(int mov) {
    movimiento = mov;
  }

  public synchronized int getMovimiento() {
    return movimiento;
  }

  //Implementamos la interface MouseListener.
  //Solo nos ocuparemos cuando se clickea con el mouse, los demas eventos no se atienden.
  public void mouseReleased(MouseEvent e){}
  public void mousePressed(MouseEvent e){}
  public void mouseEntered(MouseEvent e){}
  public void mouseExited(MouseEvent e){}

  public void mouseClicked(MouseEvent e){
    if (finJuego) {
      paraReloj();
      tableroInicial();
      arrancaReloj();
      return;
    }

    if ((e.getModifiers() & MouseEvent.SHIFT_MASK) == MouseEvent.SHIFT_MASK) {
         setMovimiento(ROTAR);
    } else if ((e.getModifiers() & MouseEvent.CTRL_MASK) ==  MouseEvent.CTRL_MASK) {
         setMovimiento(ABAJO);
    } else if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) == MouseEvent.BUTTON1_MASK) {
         setMovimiento(IZDA);
    } else if ((e.getModifiers() & MouseEvent.BUTTON3_MASK) == MouseEvent.BUTTON3_MASK) {
         setMovimiento(DCHA);
    }
  }
  //Implementamos la interface KeyListener
  //Solo escucharemos al presionar una tecla.
  public void keyTyped(KeyEvent e){}
  public void keyReleased(KeyEvent e){}

  public void keyPressed(KeyEvent e) {
    if (finJuego) return;
    if (e.getKeyCode() == KeyEvent.VK_SPACE) {
        setMovimiento(ROTAR);
    } else if (e.getKeyCode() == KeyEvent.VK_LEFT) {
        setMovimiento(IZDA);
    } else if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
        setMovimiento(DCHA);
    } else if (e.getKeyCode() == KeyEvent.VK_DOWN) {
        setMovimiento(ABAJO);
    } else if (e.getKeyCode() == KeyEvent.VK_ENTER) {
        setMovimiento(ABAJOR);
    } else if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
        paraReloj();
        tableroInicial();
        arrancaReloj();
    }
  }

  //Implementamos la interface FocusListener
  public void focusGained(FocusEvent e) {
      arrancaReloj();
  }
  public void focusLost(FocusEvent e) {
      paraReloj();
  }

}

//La clase Reloj es un trhead que se usa para la animacion.
class Reloj extends Thread {
  static final int VELOCIDAD = 2;
  Tetris tetris;        //referencia al applet
  int tiempo = 1000;    //delay

  //constructor
  public Reloj(Tetris tetris, int tiempo) {
    this.tetris = tetris;
    this.tiempo = tiempo;
  }

  //Aqui se realiza el cuerpo de la animacion
  public void run() {
    long antes;
    long ahora;
    int tgc = 10;

    antes = System.currentTimeMillis();
    while(true) {
      switch (tetris.getMovimiento()) {
        case Tetris.ABAJO : tetris.abajoLento(); break;
        case Tetris.ABAJOR : tetris.abajoDeprisa(); break;
        case Tetris.IZDA : tetris.izquierda(); break;
        case Tetris.DCHA : tetris.derecha(); break;
        case Tetris.ROTAR : tetris.rotar(); break;
      }
      ahora = System.currentTimeMillis();
      if (ahora - antes > tiempo) {
        if (--tgc < 0) { Runtime.getRuntime().gc(); tgc = 10; }
        tetris.abajo();
        if (tiempo > 100) tiempo -= VELOCIDAD;
        antes = ahora;
      }
    }
  }
}

